# -*- coding: utf-8 -*-
u"""
Utilitaires variés pour pylot.qtgui
"""

#This file is part of pyLot library.
#
# pyLot is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# pyLot is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with pyLot.  If not, see <http://www.gnu.org/licenses/>.

__author__ = u'Pierre Puiseux, Guillaume Baty'
__copyright__ = u"Copyright 2011-2012 (C) andheo, Université de Pau et des Pays de l'Adour"
__credits__ = [u'Pierre Puiseux', u'Guillaume Baty']
__license__ = "GNU Lesser General Public License"

__all__ = [
  'connect_widget_value_changed',
  'create_background_palette',
  'fill',
  'generate_pyfile_from_uifile',
  'get_icon',
  'get_named_font'
  'get_widget_prefix',
  'get_widget_value',
  'make_error_dialog',
  'qcolor_to_qstring',
  'qstring_to_qcolor',
  'set_qt_widget_text',
  'set_qt_widget_texts',
  'set_widget_value',
  'widget_state_export_raw',
  'widget_state_import_raw',
  ]

try :
  tr_
except NameError :
  tr_ = lambda s:s

import sys
import pickle

from Qt4 import QtGui, QtCore
from Qt4.QtGui import (QToolBar, QMenu, QAction, QMenuBar, QGroupBox, QIcon,
                         QPalette, QBrush, QColor, QWidget, QToolButton)


from jinja2 import Markup

from pyLot.core import (ENCODING, Path, good_level_2, error,
                        LOGGER, log_exception, DirPath, FilePath)

from .designer import generate_pyfile_from_uifile

# Naming convention
widget_prefix_dict = {
  QtGui.QCheckBox:'cb_',
  QtGui.QLineEdit:'e_', #e: entry
  QtGui.QComboBox:'cb_',
  QtGui.QSpinBox:'sb_',
  QtGui.QDoubleSpinBox:'sb_',
  QtGui.QLabel:'l_',
}

widget_getter_dict = {
  # widget : (get method, converter) 
  # converter = None : no conversion
  QtGui.QCheckBox:('isChecked', None),
  QtGui.QLineEdit:('text', unicode),
  QtGui.QComboBox:('currentIndex', None),
  QtGui.QSpinBox:('value', None),
  QtGui.QDoubleSpinBox:('value', None),
}

widget_value_changed_dict = {
  # widget : (signal, prefered signal type) 
  # prefered signal=None : use default
  QtGui.QCheckBox:'stateChanged(int)',
  QtGui.QLineEdit:'editingFinished()',
  QtGui.QComboBox:'currentIndexChanged(int)',
  QtGui.QSpinBox:'valueChanged(int)',
  QtGui.QDoubleSpinBox:'valueChanged(int)',
  }

# automatically adds set"Getter" methods. ex text -> setText
widget_setter_dict = {}
for qclass, attrs in widget_getter_dict.iteritems() :
  method, converter = attrs
  widget_setter_dict[qclass] = 'set%s%s' % (method[0].upper(), method[1:])


# But there are some exceptions
widget_setter_dict[QtGui.QCheckBox] = 'setChecked'
ignored = []

widgets_ok_for_debug = (QtGui.QWidget, QtGui.QLayout, QtGui.QAction,
                 QtGui.QMenu)

class Dialog(QtGui.QDialog):
  def __init__(self, widget):
    QtGui.QDialog.__init__(self)
    self.bbox = QtGui.QDialogButtonBox()
    self.bbox.setStandardButtons(QtGui.QDialogButtonBox.Cancel | QtGui.QDialogButtonBox.Ok)
    self.connect(self.bbox, QtCore.SIGNAL('accepted()'), self.accept)
    self.connect(self.bbox, QtCore.SIGNAL('rejected()'), self.reject)

    self.layout_main = QtGui.QVBoxLayout(self)
    self.layout_main.addWidget(widget)
    self.layout_main.addWidget(self.bbox)

    self.widget = widget

def selector_to_dialog(widget):
  u"""
  .. warning ::
  
    Ne fonctionne qu'avec les widget ayant une méthode "value"
  """
  dialog = Dialog(widget)
  if dialog.exec_() :
    return dialog.widget.value()
  else :
    return None

def test_widget_as_dialog(widget_class):

  app = QtGui.QApplication(sys.argv)

  widget = widget_class()
  print selector_to_dialog(widget)
  widget.show()

  return app.exec_()


def get_icon(filename, additionnal_dirs=[]):
  try :
    from _pylot.defaultconf import hardbook
  except ImportError :
    icon_path = Path(u'pylot', u'share', u'icons', filename)
  else :
    icon_path = hardbook(u'icons', filename)
  if icon_path.isfile() :
    return QIcon(icon_path)
  else :
    for path in additionnal_dirs :
      icon_path = Path(path, filename)
      if icon_path.isfile() :
        return QIcon(icon_path)
    return QIcon(icon_path) # No icon


def make_error_dialog(e, parent=None):
  try :
    title = e.getTitle()
  except AttributeError :
    title = u'Error'

  try :
    msg = e.getMessage()
  except AttributeError :
    msg = u'An error occurs'

  try :
    desc = e.getDesc()
  except AttributeError :
    desc = None

  mbox = QtGui.QMessageBox(parent)
  if desc :
    mbox.setDetailedText(desc)
  mbox.setText(msg)
  mbox.setWindowTitle(title)
  mbox.setStandardButtons(QtGui.QMessageBox.Ok)
  mbox.setDefaultButton(QtGui.QMessageBox.Ok)
  mbox.setIcon(QtGui.QMessageBox.Critical)
  return mbox.exec_()

def fill(widget, actions, current_level=u'expert', clear=True):
  u"""
  This methods simplify menu and toolbar filling.
  Fill a widget (like menu, toolbars) with given actions.
  action is a list of QAction, QMenu or special keywords.

  Allowed keywords : '-', add a separator

  :param widget: the widget you want to fill
  :type widget: QToolBar, QMenuBar, QMenu
  :param action: list of "actions" (QAction, QMenu, or "-")
  :type action: QAction, QMenu or str
  """
  curlevel = current_level
  supported_qwidgets = (QMenu, QAction, QMenuBar, QToolBar, QToolButton)
  if not issubclass(type(widget), supported_qwidgets) :
    classes = u', '.join([unicode(c) for c in supported_qwidgets])
    raise ValueError, error(tr_(u'widget: %s required, got %s') % (classes, type(widget)))
  if clear :
    widget.clear()

  for action in actions :
    level = u'user'
    if isinstance(action, tuple) :
      if len(action) >= 2 :
        level = action[1]
        action = action[0]
      else :
        pass
    if isinstance(action, unicode) :
      if action.lower() in (u'-',) and good_level_2(curlevel, level) :
        widget.addSeparator()
      elif action.lower() in (u'-',) :
        pass
      else :
        raise NotImplementedError
    elif isinstance(action, QAction) and good_level_2(curlevel, level) :
      widget.addAction(action)
    elif isinstance(action, QMenu) and good_level_2(curlevel, level) :
      widget.addMenu(action)
    elif isinstance(action, QWidget) and good_level_2(curlevel, level) :
      raise ValueError, tr_(u'fill method only support QWidgetAction for widgets')
    else :
      pass

def create_background_palette (qcolor):
  bg = QPalette()
  bg.setBrush(QPalette.Active, QPalette.Base, QBrush(qcolor))
  bg.setBrush(QPalette.Inactive, QPalette.Base, QBrush(qcolor))
  return bg

def qstring_to_qcolor (qstring):
  u"""
  ex: QString("255,255,255,255") -> QColor(255, 255, 255, 255)

  @type qstring: QString
  @rtype: QColor
  """
  RGBA = unicode(qstring)
  RGBA = [int(c) for c in RGBA.split(u',')]
  return QColor(*RGBA)

def add_developer_infos(widget, current_level=0, text=u'', depth=10):
  u"""
  Add developer information as tooltips for all child widgets
  """
  try :
    _tt = widget.whatsThis()
  except AttributeError :
    cobj = tt = text
  else :
    # text = u "Previous text"
    # _tt = u'Normal tooltip'
    # ctt = u' - '
    info_class = unicode(widget.__class__)[8:-2]
    info_obj = unicode(widget.objectName())
    cobj = ' %s - (%s) %s\n' % (hex(id(widget)), info_class, info_obj)
    # ctt = u' - (QtGui.QWidget) widget1'
    tt = u' %s %s' % (text, cobj)
    # ctt = u"""Normal tooltip
    #   Previous text
    #   -(QtGui.QWidget) widget1

    # Detailed informations
    infos = u'\n - class: %s' % info_class
    infos += u'\n - object name: %s' % info_obj
    infos += u'\n - address: %s' % hex(id(widget))

    widget.setToolTip(Markup.escape(_tt + tt + infos))
    widget.setWhatsThis(Markup.escape(_tt + tt + infos))

    if current_level >= depth :
      return

    for child_widget in widget.children() :
      if isinstance(child_widget, widgets_ok_for_debug) :
        add_developer_infos(child_widget, current_level + 1, tt, depth)
      else :
        klass = type(child_widget)
        if klass not in ignored :
          ignored.append(klass)
          sys.__stdout__.write(' %s\n' % klass)

def generate_pixmap(text, size=(128, 128)):
  pixmap = QtGui.QPixmap(*size)
  pixmap.fill()
  painter = QtGui.QPainter()
  painter.begin(pixmap)
  painter.setPen(QtCore.Qt.blue)
  painter.drawText(32, 32, text)
  painter.end()
  return pixmap

def fill_menu (menu, action_list, clear):
  if clear :
    menu.clear()
  for action in action_list :
    if isinstance(action, QtGui.QAction) :
      menu.addAction(action)
    elif isinstance(action, QtGui.QMenu) :
      menu.addMenu(action)
    elif isinstance(action, unicode) :
      if action == u' - ' :
        menu.addSeparator()

def get_widget_prefix(widget):
  try :
    prefix = widget.prefix
  except AttributeError:
    try :
      prefix = widget_prefix_dict[type(widget)]
    except KeyError :
      return u''
    else :
      return prefix
  else :
    return prefix


def get_widget_value(widget):
  try :
    value = widget.value()
  except AttributeError :
    try :
      method, conv = widget_getter_dict[type(widget)]
    except KeyError :
      LOGGER.debug('%s is not supported' % type(widget).__name__)
      value = None
    else :
      value = getattr(widget, method)()

  # Try to convert to a default python type
  # typically QString -> unicode
  try :
    method, conv = widget_getter_dict[type(widget)]
    if conv : value = conv(value)
  except KeyError :
    pass

  return value


def connect_widget_value_changed(widget, method):
  try :
    widget.valueChanged
    if type(widget) in widget_value_changed_dict :
      raise AttributeError
    widget.connect(widget, QtCore.SIGNAL('valueChanged()'), method)
  except AttributeError :
    try :
      signal_method = widget_value_changed_dict[type(widget)]
    except KeyError :
      LOGGER.debug('%s is not supported' % type(widget).__name__)
      return False
    else :
      # widget is supported
      try :
        widget.connect(widget, QtCore.SIGNAL(signal_method), method)
      except (ValueError, TypeError, AttributeError), error :
        # But method type is wrong or signal don't support new style signal
        LOGGER.debug(log_exception(error))
        args = (widget.objectName(),)
        LOGGER.debug('%s do not support "valueChanged" signal' % args)
        return False
      else :
        return True
  else :
    # widget has a new style signal method called "valueChanged"
    return True

def set_widget_value(widget, value):
  error = None
  try :
    widget.setValue(value)
  except AttributeError :
    try :
      method = widget_setter_dict[type(widget)]
    except KeyError :
      LOGGER.debug('%s is not supported' % type(widget).__name__)
    else :
      try :
        getattr(widget, method)(value)
      except ValueError, error :
        pass
      else :
        return True
  except (ValueError, TypeError), error :
    pass
  else :
    return True

  if error :
    LOGGER.debug(log_exception(error))
    args = (widget.objectName(), type(value).__name__)
    LOGGER.debug('%s do not support %s' % args)
    return False

def widget_state_to_dict(widget):
  vals = {}
  for child in dir(widget) :
    w = getattr(widget, child)
    if not isinstance(w, QtGui.QWidget) :
      continue
    try :
      val = w.value()
    except AttributeError :
      if isinstance(w, tuple(widget_getter_dict)) :
        val = get_widget_value(w)
      else :
        # Maybe this widget contains subwidget, recurse
        for subwidget in w.children() :
          vals.update(widget_state_to_dict(subwidget))
        val = None

    if val is not None :
      if isinstance(val, QtCore.QString) :
        val = unicode(val)
        val_disp = val
      elif isinstance(val, str):
        val_disp = val.decode(ENCODING)
      else :
        val_disp = unicode(val)
      LOGGER.debug(u'Valeurs sauvegardées : % s % s % s' % (
                   val.__class__.__name__.ljust(10),
                   str(w.objectName()).ljust(20), val_disp))
      vals[child] = val
  return vals

def widget_state_export_raw(widget, filepath):
  f = open(filepath, 'w')
  vals = widget_state_to_dict(widget)
  pickle.dump(vals, f)
  f.close()

def widget_state_import_raw(widget, filepath):
  f = open(filepath, 'r')
  vals = pickle.load(f)
  ok, tot = widget_state_up_from_dict(widget, vals)
  if float(ok) / tot < 0.5 :
    LOGGER.info(u'Le fichier de sauvegarde % s ne correspond pas au plugin courant ! ' % filepath)
  else :
    LOGGER.info(u'Fichier de sauvegarde % s chargé avec succès ! ' % filepath)
  f.close()


def widget_state_up_from_dict(widget, vals):
  cpt = 0.
  for name, value in vals.iteritems() :
    try :
      w = getattr(widget, name)
    except AttributeError, error :
      LOGGER.debug(log_exception(error))
      args = value, name
      LOGGER.debug(u'Impossibilité d\'affecter la valeur "%s", le widget "%s" n\'existe pas' % args)
    else :

      try :
        method = w.setValue
      except AttributeError :
        if isinstance(w, tuple(widget_setter_dict)) :
          set_widget_value(w, value)
      else :
        method(value)
    cpt += 1
  return cpt, len(vals)

def get_named_font (name, char_formats):
  u"""
  get font of 'format' called 'name'. If not exists, return 'std' font.
  'format' is an element of formats attribute. see L{GuipyLotManager._createFonts}

  :param name: format name (see self.formats)
  :type name: str

  :return: format font
  :rtype: QFont
  """
  try:
    cformat = char_formats[name]
  except KeyError :
    cformat = char_formats[u'std']
  return cformat.font()

def set_qt_widget_text (widget, text, field=u'text'):
  u"""
  This method recognize widget type and use good method

  :param widget: QWidget you want to translate
  :type widget: QWidget
  :param text: english text
  :type text: unicode
  :param field: what widget text you want to translate, default 'text' fields availables: tooltip, shortcut, text
  :type field: unicode
  """
  if field == u'text' :
    try :
      widget.setText(text)
    except AttributeError :
      if isinstance(widget, (QMenuBar, QMenu, QGroupBox)) :
        widget.setTitle(text)
      elif isinstance(widget, QWidget) :
        widget.setWindowTitle(text)
      else :
        raise AttributeError, error(tr_(u'%s do not support setText" method') % widget)

  elif field == u'tooltip':
    try :
      widget.setToolTip(text)
    except AttributeError :
      raise AttributeError, error(tr_(u'%s do not support "setTooltip" method') % widget)

  elif field == u'shortcut':
    try :
      widget.setShortcut(text)
    except AttributeError :
      raise AttributeError, error(tr_(u'%s do not support "setShortcut" method') % widget)

  elif field == u'whatsthis' :
    try :
      widget.setWhatsThis(text)
    except AttributeError :
      raise AttributeError, error(tr_(u'%s do not support "setWhatsThis" method') % widget)

  else :
    raise ValueError, error(tr_(u'Unknown field %r') % field)

def set_qt_widget_texts (widget_text_list, field=u'text'):
  u"""
  :param widget_trad_list: list of cuple (QWidget, text)
  """
  for couple in widget_text_list :
    widget, text = couple
    set_qt_widget_text(widget, text, field)

def qcolor_to_qstring (qcolor):
  u"""
  ex: QColor(255, 255, 255) -> QString("255,255,255,255")

  @type qcolor: QColor
  @rtype: QString
  """
  RGBA = [qcolor.red(), qcolor.green(), qcolor.blue(), qcolor.alpha()]
  RGBA = [unicode(c) for c in RGBA]
  return u','.join(RGBA)

